package com.atguigu.gmall.product.service.impl;

/**
 * @author: atguigu
 * @create: 2023-04-19 11:33
 */

import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.rabbit.config.MqConst;
import com.atguigu.gmall.common.rabbit.service.RabbitService;
import com.atguigu.gmall.product.model.SkuAttrValue;
import com.atguigu.gmall.product.model.SkuImage;
import com.atguigu.gmall.product.model.SkuInfo;
import com.atguigu.gmall.product.model.SkuSaleAttrValue;
import com.atguigu.gmall.product.service.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class SkuManageServiceImpl implements SkuManageService {


    @Autowired
    private SkuInfoService skuInfoService;

    @Autowired
    private SkuImageService skuImageService;

    @Autowired
    private SkuAttrValueService skuAttrValueService;

    @Autowired
    private SkuSaleAttrValueService skuSaleAttrValueService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitService rabbitService;


    /**
     * 新增保存商品SKU信息
     * 1.将提交的商品SKU基本信息封装为SkuInfo对象 存入sku_info表
     * 2.将提交商品SKU图片封装为SkuImage集合 存入sku_image表
     * 3.将提交商品SKu平台属性信息 封装 SkuAttrValue集合 存入sku_attr_value表
     * 4.将提交商品SKu销售属性信息 封装 SkuSaleAttrValue集合 存入sku_sale_attr_value表
     *
     * @param skuInfo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveSkuInfo(SkuInfo skuInfo) {
        //1.将提交的商品SKU基本信息封装为SkuInfo对象 存入sku_info表
        skuInfoService.save(skuInfo);
        Long skuId = skuInfo.getId();
        //2.将提交商品SKU图片封装为SkuImage集合 存入sku_image表
        List<SkuImage> skuImageList = skuInfo.getSkuImageList();
        if (!CollectionUtils.isEmpty(skuImageList)) {
            skuImageList.stream().forEach(skuImage -> {
                skuImage.setSkuId(skuId);
            });
            skuImageService.saveBatch(skuImageList);
        }

        //3.将提交商品SKu平台属性信息 封装 SkuAttrValue集合 存入sku_attr_value表
        List<SkuAttrValue> skuAttrValueList = skuInfo.getSkuAttrValueList();
        if (!CollectionUtils.isEmpty(skuAttrValueList)) {
            skuAttrValueList.stream().forEach(skuAttrValue -> {
                skuAttrValue.setSkuId(skuId);
            });
            skuAttrValueService.saveBatch(skuAttrValueList);
        }
        //4.将提交商品SKu销售属性信息 封装 SkuSaleAttrValue集合 存入sku_sale_attr_value表
        List<SkuSaleAttrValue> skuSaleAttrValueList = skuInfo.getSkuSaleAttrValueList();
        if (!CollectionUtils.isEmpty(skuSaleAttrValueList)) {
            skuSaleAttrValueList.stream().forEach(skuSaleAttrValue -> {
                skuSaleAttrValue.setSkuId(skuId);
                skuSaleAttrValue.setSpuId(skuInfo.getSpuId());
            });
            skuSaleAttrValueService.saveBatch(skuSaleAttrValueList);
        }

        //5.将新增商品SKUID存入布隆过滤器 用于判断商品不存在
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConst.SKU_BLOOM_FILTER);
        bloomFilter.add(skuId);
    }

    /**
     * 根据分类ID查询商品SKU列表
     *
     * @param infoPage    分页对象
     * @param category3Id
     * @return
     */
    @Override
    public Page<SkuInfo> getSkuListByPage(Page<SkuInfo> infoPage, Long category3Id) {
        //1.构建查询条件 排序条件
        LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SkuInfo::getCategory3Id, category3Id);

        queryWrapper.orderByDesc(SkuInfo::getUpdateTime);
        //2.执行分页查询
        Page<SkuInfo> page = skuInfoService.page(infoPage, queryWrapper);
        return page;
    }

    /**
     * 商品上架
     *
     * @param skuId
     * @return
     */
    @Override
    public void onSale(Long skuId) {
        SkuInfo skuInfo = new SkuInfo();
        skuInfo.setId(skuId);
        skuInfo.setIsSale(1);
        skuInfoService.updateById(skuInfo);
        //商品上架成功后,需要同步更新ES索引库;Redis缓存;
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_UPPER, skuId);
    }

    /**
     * 商品下架
     *
     * @param skuId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelSale(Long skuId) {
        //sql update sku_info set is_sale = 0 where id = ?
        LambdaUpdateWrapper<SkuInfo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SkuInfo::getId, skuId);
        updateWrapper.set(SkuInfo::getIsSale, 0);
        skuInfoService.update(updateWrapper);
        //商品下架成功后,需要同步更新ES索引库;Redis缓存;
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_LOWER, skuId);
    }


    /**
     * 利用缓存获取商品信息,采用分布式锁避(Redisson)免缓存击穿
     *
     * @param skuId
     * @return
     */
    //@Override
    public SkuInfo getSkuInfoByRedisson(Long skuId) {
        try {
            //1.优先从分布式缓存Redis中获取数据
            //1.1 构建缓存业务数据商品信息Key 形式 sku:商品ID:info
            String dataKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            //1.2 调用模板对象获取缓存中数据
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);
            //1.3 判断命中结果有值直接返回
            if (skuInfo != null) {
                return skuInfo;
            }
            //2.尝试获取分布式锁
            //2.1 构建锁key sku:商品ID:lock   锁的值:UUID  获取锁对象
            String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
            //String lockVal = UUID.randomUUID().toString().replaceAll("-", "");
            //2.2 尝试获取锁
            RLock lock = redissonClient.getLock(lockKey);
            //lock.lock();
            boolean flag = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX2, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
            //2.3 获取锁成功执行第三步业务
            if (flag) {
                //3.执行业务
                //3.1 执行查询数据库操作
                try {
                    skuInfo = this.getSkuInfoFromDB(skuId);
                    //3.2 未查询到业务数据 将空的结果缓存到redis 存10分钟
                    if (skuInfo == null) {
                        redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo;
                    } else {
                        //3.3 查询到业务数据 将业务结果缓存到redis 存24小时
                        redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo;
                    }
                } finally {
                    //3.4 最后保证锁释放
                    lock.unlock();
                }
            } else {
                //2.4 获取锁失败自旋
                TimeUnit.MILLISECONDS.sleep(500);
                return this.getSkuInfoByRedisson(skuId);
            }
        } catch (Exception e) {
            //如果有不可预知错误,Redis服务异常,兜底处理
            return this.getSkuInfoFromDB(skuId);
        }
    }

    /**
     * 利用缓存获取商品信息,采用分布式锁避(SpringDataRedis)免缓存击穿
     *
     * @param skuId
     * @return
     */
    public SkuInfo getSkuInfoBySpringDataRedis(Long skuId) {
        try {
            //1.优先从分布式缓存Redis中获取商品信息
            //1.1 构建缓存业务数据Key 形式 sku:1:info
            String dataKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            //1.2 从缓存中获取业务数据
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);

            //1.3 如果命中缓冲-则直接响应数据接口
            if (skuInfo != null) {
                return skuInfo;
            }
            //2.尝试获取分布式锁
            //2.1 构建某件商品分布式锁Key 形式  sku:1:lock
            String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
            //2.2 构建分布式锁值 采用UUID 避免锁互相释放
            String lockVal = UUID.randomUUID().toString().replaceAll("-", "");
            //2.3 通过set ex nx 命令实现分布式锁
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
            //2.4 如果获取锁成功-进行第3步
            if (flag) {
                //3.执行业务-记得释放锁
                //3.1 查询数据库记录
                try {
                    skuInfo = this.getSkuInfoFromDB(skuId);
                    //3.2 查询数据不存在,将空业务数据放入缓存 缓存 短暂时间 10分钟
                    if (skuInfo == null) {
                        redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo;
                    } else {
                        //3.4 查询数据存在,将数据放入缓存 真实业务数据存储24小时
                        redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo;
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //3.5 将锁释放 采用lua脚本释放锁 保证判断跟删除锁原子性
                    //redisTemplate.delete(lockKey);
                    //3.5.1 创建脚本对象 封装lua脚本语言  KEYS[1]:传入锁的名称  ARGV[1]:传入当前线程锁的uuid值
                    String luaText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                            "then\n" +
                            "    return redis.call(\"del\",KEYS[1])\n" +
                            "else\n" +
                            "    return 0\n" +
                            "end";
                    DefaultRedisScript<Long> script = new DefaultRedisScript<>();
                    script.setScriptText(luaText);
                    script.setResultType(Long.class);
                    //4.2 为脚本中动态占位参数设置值
                    //4.3 执行lua脚本
                    redisTemplate.execute(script, Arrays.asList(lockKey), new String[]{lockVal});
                }
            } else {
                //2.5 如果获取锁失败-进行自旋-尝试再次获取
                TimeUnit.MILLISECONDS.sleep(500);
                return this.getSkuInfoBySpringDataRedis(skuId);
            }
        } catch (Exception e) {
            //如果有不可预知错误,Redis服务异常,兜底处理
            return this.getSkuInfoFromDB(skuId);
        }
    }

    /**
     * 获取商品信息 包含商品图片
     *
     * @param skuId
     * @return
     */
    @Override
    @GmallCache(prefix = "sku:", suffix = ":info")
    public SkuInfo getSkuInfoFromDB(Long skuId) {
        //1.根据主键SKUID查询商品SKU基本信息
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        if (skuInfo != null) {
            //2.根据SkuID查询商品SKu图片列表 封装 SkuInfo对象中
            LambdaQueryWrapper<SkuImage> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SkuImage::getSkuId, skuId);
            List<SkuImage> skuImageList = skuImageService.list(queryWrapper);
            skuInfo.setSkuImageList(skuImageList);
            return skuInfo;
        }
        return null;
    }


    /**
     * 根据skuID查询商品价格
     *
     * @param skuId
     * @return
     */
    @Override
    public BigDecimal getSkuPrice(Long skuId) {
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        if (skuInfo != null) {
            return skuInfo.getPrice();
        }
        return new BigDecimal("0.00");
    }


}
